﻿using System;
using System.ComponentModel;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;

namespace T3K.SketchEditor.Core {

    [Serializable]
    [DefaultProperty("ProjectName")]
    public class Project : ICustomTypeDescriptor {

        ProjectNode _rootNode;
        string _projectDir = null;
        string _projectFilePath = null;
        string _projectName = null;
        List<ProjectProperty> _properties = new List<ProjectProperty>();
        static int _numberSeed = 0;

        [Browsable(false)]
        public ProjectNode RootNode {
            get { return _rootNode; }
            set { _rootNode = value; }
        }

        [DisplayName("Name")]
        [Description("Project Name")]
        [ReadOnly(true)]
        [Category("Properties")]
        public string ProjectName {
            get { return _projectName; }
            set { 
                _projectName = value;
                _rootNode.Name = _projectName;
            }
        }

        [DisplayName("Directory")]
        [Description("Project Directory")]
        [ReadOnly(true)]
        [Category("Properties")]
        public string ProjectDir {
            get { return _projectDir; }
        }

        [DisplayName("File Path")]
        [Description("Project File Path")]
        [ReadOnly(true)]
        [Category("Properties")]
        [XmlIgnore]
        public string ProjectFilePath {
            get { return _projectFilePath; }
            set {
                if (!string.IsNullOrEmpty(value)) {
                    _projectFilePath = value;
                    FileInfo fInfo = new FileInfo(_projectFilePath);
                    _projectDir = fInfo.Directory.FullName;
                    _rootNode.ProjectPath = _projectDir;
                }
            }
        }

        [Browsable(false)]
        public virtual string this[string key] {
            get {

                if (key != null) {
                    for (int i = 0; i < _properties.Count; i++)
                        if (_properties[i].Name == key)
                            return _properties[i].Value;
                }
                return null;

            }
            set {

                if (key != null) {
                    for (int i = 0; i < _properties.Count; i++)
                        if (_properties[i].Name == key) {
                            _properties[i].Value = value;
                            return;
                        }

                    _properties.Add(new ProjectProperty(key, value));
                }
                    
            }
        }

        [Browsable(false)]
        public List<ProjectProperty> Properties {
            get { return _properties; }
            set {
                if (value != null) {
                    for (int i = 0; i < value.Count; i++) {
                        this[value[i].Name] = value[i].Value;
                    }
                }
            }
        }

        public Project() {
            _projectName = string.Format("untitled{0}.skt", ++_numberSeed);
            _rootNode = new ProjectNode(_projectName, ProjectNodeType.Project, "", null);
        }

        [Serializable]
        public enum ProjectNodeType {
            CodeFile, HeaderFile, Folder, Project, Include, Other
        }

        [Serializable]
        [DefaultProperty("Name")]
        public class ProjectNode {
            List<ProjectNode> _nodes;
            string _name;
            ProjectNodeType _nodeType;
            ProjectNode _parentNode;
            string _projectPath;

            [Browsable(false)]
            public List<ProjectNode> Nodes {
                get { return _nodes; }
                set { 
                    _nodes = value;
                    if (_nodes != null) {
                        foreach (ProjectNode node in _nodes)
                            this.SetParentNode(this);
                    }
                }
            }

            [DisplayName("Name")]
            [Description("Project Folder/File Name")]
            [ReadOnly(true)]
            [Category("Properties")]
            public string Name {
                get { return _name; }
                set { _name = value; }
            }

            [Browsable(false)]
            public ProjectNodeType NodeType {
                get { return _nodeType; }
                set { _nodeType = value; }
            }

            [Browsable(false)]
            public ProjectNode ParentNode {
                get { return _parentNode; }
            }

            [DisplayName("File Path")]
            [Description("Project Folder/File Path")]
            [ReadOnly(true)]
            [Category("Properties")]
            public string FilePath {
                get {
                    ProjectNode node = this;
                    Stack<string> path = new Stack<string>();
                    do {
                        path.Push(node._name);
                        node = node._parentNode;
                    } while (node != null && node._nodeType != ProjectNodeType.Project); //Ignore the root node

                    path.Push(_projectPath);

                    string[] tokens = path.ToArray();
                    FileInfo fInfo = new FileInfo(string.Join("/", tokens));

                    return fInfo.FullName;
                }
            }

            [DisplayName("Project Path")]
            [Description("Project Path")]
            [ReadOnly(true)]
            [Category("Properties")]
            [XmlIgnore]
            public string ProjectPath {
                get { return _projectPath; }
                set { 
                    _projectPath = value;
                    foreach (ProjectNode node in this._nodes)
                        node.ProjectPath = value;

                }
            }

            [Browsable(false)]
            public void SetParentNode(ProjectNode node) {
                this._parentNode = node;
            }

            protected ProjectNode() {
                _nodes = new List<ProjectNode>();
            }

            public ProjectNode(string name, ProjectNodeType nodeType, string projectPath, ProjectNode parentNode) {

                Utils.CheckNotNull(name, "name", true);

                _name = name;
                _nodeType = nodeType;
                _nodes = new List<ProjectNode>();
                _projectPath = projectPath;
                _parentNode = parentNode;
            }
        }

        public List<string> GetSourceFiles() {
            List<string> sources = new List<string>();
            GetSourceFiles(_rootNode, sources);
            return sources;
        }

        private void GetSourceFiles(ProjectNode node, List<string> sources) {
            if (sources != null) {

                if (node.NodeType == ProjectNodeType.CodeFile)
                    sources.Add(node.FilePath);

                for (int i = 0; i < node.Nodes.Count; i++)
                    GetSourceFiles(node.Nodes[i], sources);

            }
        }

        public void AddNode(string name, ProjectNodeType nodeType) {
            _rootNode.Nodes.Add(new ProjectNode(name, nodeType, _projectDir, _rootNode));
        }

        public void AddNode(string name, ProjectNodeType nodeType, string projectPath) {
            projectPath = string.Format("{1}/{0}", projectPath, _projectName);

            List<ProjectNode> nodes = new List<ProjectNode>();
            nodes.Add(_rootNode);

            string[] pathTokens = projectPath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            ProjectNode parentNode = null;
            for (int i = 0; i < pathTokens.Length; i++) {
                for (int n = 0; n < nodes.Count; n++) {
                    if (nodes[n].Name == pathTokens[i]) {
                        parentNode = nodes[n];
                        nodes = nodes[n].Nodes;
                    }
                }
            }

            if (parentNode.NodeType != ProjectNodeType.Project && parentNode.NodeType != ProjectNodeType.Folder)
                parentNode = parentNode.ParentNode;

            ProjectNode newNode = new ProjectNode(name, nodeType, parentNode.FilePath, parentNode);

            parentNode.Nodes.Add(newNode);
        }

        public void CreateFileFromTemplate(string fileName, string templateName, Dictionary<string, object> vars ) {

            try {

                FileTemplate fileTemplate = null;
                string templateText = string.Empty;

                foreach (FileTemplate tmpl in FileTemplate.GetTemplates())
                    if (tmpl.TemplateName.Equals(templateName, StringComparison.OrdinalIgnoreCase))
                        fileTemplate = tmpl;

                if (fileTemplate != null) {
                    templateText = fileTemplate.GetCode(vars);
                    FileInfo newFile = new FileInfo(Path.Combine(ProjectDir, fileName));

                    if (newFile.Directory.Exists) {
                        StreamWriter sw = new StreamWriter(newFile.Open(FileMode.Create));
                        sw.Write(templateText);
                        sw.Close();
                    } else {
                        EditorManager.Instance.InvokeCommand(
                            this, "Main.Debug.WriteLine",
                            new CommandArgs("Out", string.Format("'{0}' does not exist.", newFile.Directory.FullName))
                        );
                    }
                } else
                    EditorManager.Instance.InvokeCommand(
                        this, "Main.Debug.WriteLine", 
                        new CommandArgs("Out", string.Format("'{0}' template not found.", templateName))
                    );

            } catch (Exception ex) {
                EditorManager.Instance.InvokeCommand(
                    this, "Main.Debug.WriteLine", new CommandArgs("Out", ex.Message)
                );
            }

        }

        public void Save() {
            this["AppPath"] = System.IO.Directory.GetCurrentDirectory().Replace('\\', '/');
            FileStream fs = File.Open(_projectFilePath, FileMode.Create);
            XmlSerializer s = new XmlSerializer(typeof(Project));
            s.Serialize(fs, this);
            fs.Flush();
            fs.Close();
        }

        public static Project Open(string filename) {
            FileInfo fInfo = new FileInfo(filename);
            if (fInfo.Exists) {
                FileStream fs = File.Open(filename, FileMode.Open);
                XmlSerializer s = new XmlSerializer(typeof(Project));
                Project p = s.Deserialize(fs) as Project;
                p.ProjectFilePath = fInfo.FullName;
                p["AppPath"] = System.IO.Directory.GetCurrentDirectory().Replace('\\', '/');
                fs.Close();
                return p;
            } else
                throw new IOException(string.Format("The file {0} doen't exist", filename));
        }


        #region ICustomTypeDescriptor Members

        public AttributeCollection GetAttributes() {
            return TypeDescriptor.GetAttributes(this, true);
        }

        public string GetClassName() {
            return TypeDescriptor.GetClassName(this, true);
        }

        public string GetComponentName() {
            return TypeDescriptor.GetComponentName(this, true);
        }

        public TypeConverter GetConverter() {
            return TypeDescriptor.GetConverter(this, true);
        }

        public EventDescriptor GetDefaultEvent() {
            return TypeDescriptor.GetDefaultEvent(this, true);
        }

        public PropertyDescriptor GetDefaultProperty() {
            return TypeDescriptor.GetDefaultProperty(this, true);
        }

        public object GetEditor(Type editorBaseType) {
            return TypeDescriptor.GetEditor(this, editorBaseType, true);
        }

        public EventDescriptorCollection GetEvents(Attribute[] attributes) {
            return TypeDescriptor.GetEvents(this, attributes, true);
        }

        public EventDescriptorCollection GetEvents() {
            return TypeDescriptor.GetEvents(this, true);
        }

        public PropertyDescriptorCollection GetProperties(Attribute[] attributes) {
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this, attributes, true);

            List<PropertyDescriptor> lProps = new List<PropertyDescriptor>();


            foreach (PropertyDescriptor prop in props)
                lProps.Add(prop);

            foreach (ProjectProperty prop in _properties) {

                Type type = (prop.Value != null) ? prop.Value.GetType() : typeof(object);
                ProjectPropertyDescriptor ppd = new ProjectPropertyDescriptor(type, prop.Name, "Project Properties");
                lProps.Add(ppd);
            }

            return new PropertyDescriptorCollection(lProps.ToArray()); 
        }

        public PropertyDescriptorCollection GetProperties() {
            return TypeDescriptor.GetProperties(this, true);
        }

        public object GetPropertyOwner(PropertyDescriptor pd) {
            return this;
        }

        #endregion

        [Serializable]
        public class ProjectProperty {
            string _name;
            string _value;

            [XmlAttribute()]
            public string Name { get { return _name; } set { _name = value; } }

            [XmlAttribute()]
            public string Value { get { return _value; } set { _value = value; } }

            public ProjectProperty(string name, string value) {
                this._name = name;
                this._value = value;
            }

            protected ProjectProperty() { }
        }

        public class ProjectPropertyDescriptor : PropertyDescriptor {

            string _property;
            string _category;
            Type _type;

            public override bool IsBrowsable { get { return true; } }
            public override bool IsLocalizable { get { return false; } }
            public override bool IsReadOnly { get { return false; } }
            public override string Category { get { return _category; } }
            public override string Description { get { return _property; } }
            public override string DisplayName { get { return _property; } }
            public override string Name { get { return _property; } }
            protected override int NameHashCode { get { return _property.GetHashCode(); } }
            public override Type ComponentType { get { return typeof(Project); } }
            public override Type PropertyType { get { return _type; } }

            public ProjectPropertyDescriptor(Type type, string property, string category) : base (property, new Attribute[0]) {
                this._property = property;
                this._category = category;
                this._type = type;
            }

            public override object GetValue(object component) {
                Project project = component as Project;
                if (project != null)
                    return project[_property];
                else
                    return null;
            }

            public override bool CanResetValue(object component) {
                return false;
            }

            public override void SetValue(object component, object value) {
                Project project = component as Project;
                if (project != null)
                    project[_property] = value as string;
            }

            public override void ResetValue(object component) { }

            public override object GetEditor(Type editorBaseType) {
                return base.GetEditor(editorBaseType);
            }

            public override bool ShouldSerializeValue(object component) {
                return false;
            }
        }
    }
}
